#include <windows.h>
#include <assert.h>
#include <stdio.h>

#include "widget.h"
#include "callback.h"
#include "delegate.h"

#ifdef BUILD_STATIC
extern int __main__(int,char**);
#endif

EXPORT widget_t *create_widget(widget_t *parent, int32_t x, int32_t y,
    uint32_t width, uint32_t height, uint8_t backbuf_flag)
{
    DWORD style = WS_VISIBLE;
    HWND hwnd = NULL;
    HWND parenthwnd = NULL;
    // create memory for the widget
    widget_t *widget = malloc(sizeof(widget_t));
    // format the parent style
    if(parent == NULL) {
        style |= WS_POPUP;
        // calculate center coordinates if x or y is negative
        if(x<0) {
            uint32_t syswidth = GetSystemMetrics(SM_CXSCREEN);
            x = syswidth/2 - width/2;
        }
        if(y<0) {
            uint32_t sysheight = GetSystemMetrics(SM_CYSCREEN);
            y = sysheight/2 - height/2;
        }
    }
    else {
        style |= WS_CHILD;
        parenthwnd = parent->hwnd;
    }
    // create the window
    hwnd = CreateWindowEx(0,"WIDGET","",style,x,y,width,height,
        parenthwnd,NULL,GetModuleHandle(0),NULL);
    assert(hwnd != NULL);
    widget->hwnd = hwnd;
    widget->width = width;
    widget->height = height;
    widget->parent = parent;
    // bind the handle with the widget
    widget->callback = callback_bind(hwnd, widget);
    // create a back buffer it the flag is set
    if(backbuf_flag == TRUE)
    {
        HDC hdc = GetDC(hwnd);

        BITMAPV4HEADER bi;
        ZeroMemory(&bi,sizeof(BITMAPV4HEADER));
        bi.bV4Size         = sizeof(BITMAPV4HEADER);
        bi.bV4Width        = width;
        bi.bV4Height       = -height;
        bi.bV4Planes       = 1;
        bi.bV4BitCount     = 32;
        bi.bV4V4Compression  = BI_BITFIELDS;
        bi.bV4RedMask      = 0x00FF0000;
        bi.bV4GreenMask    = 0x0000FF00;
        bi.bV4BlueMask     = 0x000000FF;
        bi.bV4AlphaMask    = 0xFF000000;

        widget->memhdc = CreateCompatibleDC(hdc);
        assert(widget->memhdc != (void *)0);

        widget->bmp = CreateDIBSection(hdc, (BITMAPINFO *)&bi, DIB_RGB_COLORS,
            (void **)&widget->backbuf, NULL, 0);

        assert(widget->bmp != (void *)0);
        assert(widget->backbuf != NULL);

        widget->oldbmp = (HBITMAP)SelectObject(widget->memhdc, widget->bmp);
        ReleaseDC(widget->hwnd, hdc);
    }
    // return a pointer to the widget
    return widget;
}

LRESULT CALLBACK widget_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
    switch(msg)
    {
        // user have closed this widget
        case WM_CLOSE:
        {
            callback_t *cb = callback_find(hwnd);
            if(cb->close(cb->widget) == TRUE) {
                DestroyWindow(hwnd);
                PostQuitMessage(0);
            }
        }
        break;
        case WM_DESTROY:
        {
            widget_t *widget = callback_find(hwnd)->widget;
            SelectObject(widget->memhdc, widget->oldbmp);
            DeleteDC(widget->memhdc);
            DeleteObject(widget->bmp);
        }
        break;
        // widget need to be repainted
        case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc;
            callback_t *cb = callback_find(hwnd);
            widget_t *widget = cb->widget;

            // check if there are and back buffer
            if(widget->memhdc != (void *)0) {
                RECT rect;
                GetClientRect(hwnd, &rect);
                // let the widget paints its back client
                cb->paint(widget, widget->memhdc);
                // copy the back buffer to the client area
                hdc = BeginPaint(hwnd, &ps);
                BitBlt(hdc, 0, 0, rect.right, rect.bottom, widget->memhdc,
                    0, 0, SRCCOPY);
                EndPaint(hwnd, &ps);
            }
            else {
                // let the widget paint
                hdc = BeginPaint(hwnd, &ps);
                cb->paint(widget, hdc);
                EndPaint(hwnd, &ps);
            }
        }
        break;
        case WM_ERASEBKGND:
        break;
        // user have press a key
        case WM_KEYDOWN:
        {
            callback_t *cb = callback_find(hwnd);
            cb->key_down(cb->widget, wparam);
        }
        break;
        // user have released a key
        case WM_KEYUP:
        {
            callback_t *cb = callback_find(hwnd);
            cb->key_up(cb->widget, wparam);
        }
        break;
        // move have been moves over this widget
        case WM_MOUSEMOVE:
        {
            callback_t *cb = callback_find(hwnd);
            if(cb->widget->mouse_entered_flag == FALSE)
            {
                // makes windows send WM_MOUSELEAVE if the mouse pointer
                // leave the widget client area.
                TRACKMOUSEEVENT trackleave;
                trackleave.cbSize = sizeof(TRACKMOUSEEVENT);
                trackleave.dwFlags = TME_LEAVE;
                trackleave.hwndTrack = hwnd;
                TrackMouseEvent(&trackleave);
                // call the callback function mouse enter
                cb->mouse_enter(cb->widget,LOWORD(lparam), HIWORD(lparam),
                                wparam);
                // do not call mouse enter until the mouse have again
                cb->widget->mouse_entered_flag = TRUE;
            }
            cb->mouse_move(cb->widget,LOWORD(lparam), HIWORD(lparam),
                            wparam);
        }
        break;
        // user have pressed the left mouse button
        case WM_LBUTTONDOWN:
        {
            callback_t *cb = callback_find(hwnd);
            cb->lbutton_down(cb->widget, LOWORD(lparam), HIWORD(lparam),
                             wparam);
        }
        break;
        // user have release the right mouse button
        case WM_LBUTTONUP:
        {
            callback_t *cb = callback_find(hwnd);
            cb->lbutton_up(cb->widget, LOWORD(lparam), HIWORD(lparam),
                               wparam);
        }
        break;
        // user have pressed the right mouse button
        case WM_RBUTTONDOWN:
        {
            callback_t *cb = callback_find(hwnd);
            cb->rbutton_down(cb->widget, LOWORD(lparam), HIWORD(lparam),
                             wparam);
        }
        break;
        // user have release the right mouse button
        case WM_RBUTTONUP:
        {
            callback_t *cb = callback_find(hwnd);
            cb->rbutton_up(cb->widget, LOWORD(lparam), HIWORD(lparam),
                           wparam);
        }
        break;
        // mouse pointer have left the widgets client area
        case WM_MOUSELEAVE:
        {
            callback_t *cb = callback_find(hwnd);
            cb->mouse_leave(cb->widget);
            // do not call mouse enter until the mouse have again
            cb->widget->mouse_entered_flag = FALSE;
        }
        break;
        // Notification from chiled
        case WM_COMMAND:
        {
            callback_t *cb = callback_find((HWND)lparam);
            delegate_signal(cb->widget, HIWORD(wparam), NULL);
        }
        break;
        // this proc did not handle this message, let the default proc.
        // the other messages
        default:
            return DefWindowProc(hwnd, msg, wparam, lparam);
    }
    return 0;
}

#ifdef BUILD_DLL
EXPORT int _WinMain(HINSTANCE inst, HINSTANCE preinst, LPSTR cmdline,
             INT cmdshow, int (*main_ptr)(int,char**))
#else
int WINAPI WinMain(HINSTANCE inst, HINSTANCE preinst, LPSTR cmdline,
                   INT cmdshow)
#endif
{
    // register the widget class
    WNDCLASSEX wc;
    ZeroMemory(&wc, sizeof(WNDCLASSEX));
    wc.cbSize        = sizeof(WNDCLASSEX);    
    wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND);
    wc.lpszMenuName  = 0;
    wc.style         = CS_OWNDC|CS_HREDRAW|CS_VREDRAW;;
    wc.lpfnWndProc   = widget_proc;
    wc.hInstance     = GetModuleHandle(0);
    wc.hIcon         = 0;
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.lpszClassName = "WIDGET";
    RegisterClassEx(&wc);

    // initialize callback mapping (100 ~= max number of widget used)
    callback_initialize(100);
    delegate_initialize(100);
    // call the main window function
    #ifdef BUILD_DLL
    main_ptr(__argc, __argv);
    #elif BUILD_STATIC
    __main__(__argc, __argv);
    #endif
    
    MSG msg;
    // handler incomming messages to the application
    while(GetMessage(&msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    // free memory for the widgets and callback function
    callback_finalize();
    delegate_finalize();

    // all done, return an error code if applicable
    return msg.wParam;
}

